﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Threading;
using AccessControl.Business;
using AccessControl.Common;
using AccessControl.Common.Delegates;
using AccessControl.Common.Enumerations;
using AccessControl.HardwareLibrary.EnumAndStruct;
using AccessControl.HardwareLibrary.FS84;
using AccessControl.HardwareLibrary.Gate;
using AccessControl.Sida.Business;
using AccessControl.Utility.Tools;
using AccessControl.WorkflowEngine.Properties;
using AccessControl.WorkflowEngine.FingerService;


namespace AccessControl.WorkflowEngine
{

    #region EnumAndStructs
    public struct DeviceConnectionChangedEventArgs
    {
        public int PkDevice { get; set; }
        public bool Connected { get; set; }
    }

    #endregion

    public class WorkflowEngine
    {
        #region Settings

        private static Dictionary<int, bool> workflowEntrance;

        private ExternalBuisnessCheck _externalCheckFunc = StudentAccessBuisness.IsStudentAllowed;
        public ExternalBuisnessCheck ExternalCheckFunc
        {
            get { return _externalCheckFunc; }
            set { _externalCheckFunc = value; }
        }

        private int _entranceTimeout = 5000;
        public int EntranceTimeout
        {
            get { return _entranceTimeout; }
            set { _entranceTimeout = value; }
        }

        private static int _fingerTimeoutAfterCard = 5000;
        public static int FingerTimeoutAfterCard
        {
            get { return _fingerTimeoutAfterCard; }
            set { _fingerTimeoutAfterCard = value; }
        }

        public static bool AlwaysCheckFinger { get; set; }

        private static bool _useFingerScannerBuzzer = false;
        public static bool UseFingerScannerBuzzer
        {
            get { return _useFingerScannerBuzzer; }
            set { _useFingerScannerBuzzer = value; }
        }

        #endregion

        #region Fields

        private static bool initialized;
        private string pkPersonWaitingForFinger;
        private AccessControlDS.tblEventLogDataTable eventLogTable;

        public static AccessControlDS.tblDeviceDataTable tblDevice;
        public static AccessControlDS.tblWorkflowDataTable tblWorklow;
        public static AccessControlDS.tblEventDataTable tblEvent;
        public static AccessControlDS.tblEventDataTable tblEventType;
        public static AccessControlDS dsAccessControl;

        private GateCommunicator cardReader;
        private FS84Communicator fingerScanner;

        #endregion

        #region Properties

        public AccessControlDS.tblWorkflowRow WorkflowRow { get; set; }

        #endregion

        #region Constructors

        public WorkflowEngine(AccessControlDS.tblWorkflowRow workflowRow)
        {
            if (!initialized) Initialize();

            WorkflowRow = workflowRow;
            eventLogTable = new AccessControlDS.tblEventLogDataTable();

            if(workflowEntrance==null) 
                workflowEntrance=new Dictionary<int, bool>();
            if (!workflowEntrance.ContainsKey(workflowRow.pkWorkflow))
                workflowEntrance.Add(WorkflowRow.pkWorkflow, false);
            if (!workflowEntrance.ContainsKey(workflowRow.fkTwinWorkflow))
                workflowEntrance.Add(WorkflowRow.fkTwinWorkflow, false);
            workflowEntrance[WorkflowRow.pkWorkflow] = false;
        }

        static WorkflowEngine()
        {
            AlwaysCheckFinger = false;
        }

        #endregion

        #region Events

        public event Action<AccessControlDS.tblWorkflowRow, DeviceConnectionChangedEventArgs> DeviceConnectionChanged;

        public void OnDeviceConnectionChanged(int pkDevice, bool connected)
        {
            if (DeviceConnectionChanged != null) DeviceConnectionChanged(WorkflowRow, new DeviceConnectionChangedEventArgs{ Connected = connected, PkDevice = pkDevice });
        }

        public event Action<AccessControlDS.tblWorkflowRow, AccessControlDS.tblEventLogDataTable> EventOccured;

        public void OnEventOccured()
        {

            if (EventOccured != null) EventOccured(WorkflowRow, eventLogTable);
        }

        public event Action<object, string> DataTransfered;

        public void OnDataTransfered(string data)
        {
            if (DataTransfered != null) DataTransfered(this, data);
        }

        #endregion

        #region Methods

        public void VaKonGateSabmordaro()
        {
            BeepNormal();
            cardReader.AllowGateCustom();

        }

        public static void Initialize()
        {

            dsAccessControl = new AccessControlDS();

            tblWorklow = WorkflowBusiness.SelectAllEnabled( dsAccessControl.tblWorkflow);
            tblDevice = DeviceBusiness.SelectAllEnabled(dsAccessControl.tblDevice);
            tblEvent = EventBusiness.SelectAll(dsAccessControl.tblEvent);

            initialized = true;
        }

        public void StartAsync()
        {
            var thStart = new Thread(Start);
            thStart.Start();
        }

        private void Start()
        {
            //var param = new SqlParameter("@fkWorkflow", SqlDbType.Int);
            //param.Value = WorkflowRow.pkWorkflow;



            WriteLog("Starting...", eventLevelConstants.Info);


            foreach (AccessControlDS.tblDeviceRow device in tblDevice.Select("fkWorkflow=" + WorkflowRow.pkWorkflow))
            //foreach (AccessControlDS.tblDeviceRow device in WorkflowRow.GettblDeviceRows())
            {
                switch ((DeviceTypeConstants)device.DeviceType)
                {
                    case DeviceTypeConstants.CardReader:
                        try
                        {
                            cardReader = new GateCommunicator();
                            cardReader.pkDevice = device.pkDevice;
                            cardReader.Use10BytesCardID = Settings.Default.Use10CharsRFID;
                            cardReader.ConnectionChanged += GateConnectionChanged;
                            cardReader.CardDetected += CardDetected;
                            cardReader.DataReceived += GateDataTransfered;
                            cardReader.DataSent += GateDataTransfered;
                            cardReader.Entered += PersonEntered;
                            cardReader.NotEntered += PersonNotEntered;
                            //cardReader.ServerResponseTimeoutExpired += GateServerResponseTimeoutExpired;
                            cardReader.Connect(device.DeviceIP, device.DevicePort,3);
                            cardReader.SetDefaultBeepsOff();
                            cardReader.SetEntranceTimeout(EntranceTimeout);
                            cardReader.SetServerResponseTimeout(10000);
                            cardReader.Beep(Beeps.Normal);
                        }
                        catch (Exception ex)
                        {
                            WriteLog("Workflow " + WorkflowRow.WorkflowName +
                                            " can not be be started, can not connect to card reader!\n" + ex.Message, eventLevelConstants.Error);
                        }
                        break;
                    case DeviceTypeConstants.FingerScanner:
                        try
                        {
                            fingerScanner = new FS84Communicator();
                            fingerScanner.pkDevice = device.pkDevice;
                            fingerScanner.ConnectionChanged += FingerScannerConnectionChanged;
                            fingerScanner.FingerPrintDetected += FingerDetected;
                            fingerScanner.WaitTimeoutExpired += FingerWaitTimeoutExpired;
                            fingerScanner.Connect(device.DeviceIP, device.DevicePort,3);
                            ResumeFingerCheck();
                            fingerScanner.Beep(Beeps.Normal);

                        }
                        catch (Exception ex)
                        {
                            WriteLog("Workflow " + WorkflowRow.WorkflowName +
                                            " can not be be started, can not connect to fingerprint scanner\n" + ex.Message, eventLevelConstants.Warning);
                        }
                        break;
                }

            }

            if ((WorkflowTypeConstants)WorkflowRow.WorkflowType != WorkflowTypeConstants.CheckCard &&
                fingerScanner == null)
            {
                WorkflowRow.WorkflowType = (byte)WorkflowTypeConstants.CheckCard;
                WriteLog("WorkflowType set to check finger but fingerscanner not listed in related devices", eventLevelConstants.Warning);
            }


            if (cardReader != null)
                WriteLog("Started", eventLevelConstants.Info);
            else
                WriteLog("Card reader not added to list of related devices", eventLevelConstants.Error);
        }

        private void ResumeFingerCheck()
        {
            //Return the finger wait timeout to normal if needed
            if ((AlwaysCheckFinger && (WorkflowTypeConstants)WorkflowRow.WorkflowType == WorkflowTypeConstants.CheckCardThenFinger) ||
                (WorkflowTypeConstants)WorkflowRow.WorkflowType == WorkflowTypeConstants.CheckFinger ||
                (WorkflowTypeConstants)WorkflowRow.WorkflowType == WorkflowTypeConstants.CheckCardOrFinger)
                fingerScanner.WaitForFingerprint(0);

        }

        private void CardDetected(object sender, string cardID)
        {
            try
            {
                if (!WorkflowRow.IsfkTwinWorkflowNull()
                    && workflowEntrance[WorkflowRow.fkTwinWorkflow]){
                        cardReader.DenyGateForCard();
                        WriteLog("Duplicate entrance Detected", eventLevelConstants.Info);
                        BeepNotAllowed();
                        return;
                }
                    
                workflowEntrance[WorkflowRow.pkWorkflow] = true;
                //if ((WorkflowTypeConstants) WorkflowRow.WorkflowType == WorkflowTypeConstants.CheckFinger){
                //    cardReader.DenyGateForCard();
                //    BeepNotAllowed();
                //    return;
                //} 




                string reason;
                var pkPerson = cardID;
                var isFingerRequired = (WorkflowTypeConstants)WorkflowRow.WorkflowType ==
                                    WorkflowTypeConstants.CheckCardThenFinger ||
                                    (WorkflowTypeConstants)WorkflowRow.WorkflowType ==
                                    WorkflowTypeConstants.CheckFinger;

                var personAllowed = PersonEventBuisness.CheckPersonByCard(ref pkPerson, ref isFingerRequired, ExternalCheckFunc, WorkflowRow.tblEventRow.fkEventType, WorkflowRow.fkEvent, out reason);

                if (fingerScanner == null) isFingerRequired = false;


                if (!string.IsNullOrWhiteSpace(pkPerson))
                    EventLogBusiness.ReportPersonAccessResult(pkPerson, ref eventLogTable, WorkflowRow.pkWorkflow, personAllowed ? EventResultConstants.Authorized : EventResultConstants.Denied, reason, WorkflowRow.tblEventRow.EventName);

                if (personAllowed && isFingerRequired)
                {
                    pkPersonWaitingForFinger = pkPerson;

                    //cardReader.DenyGateForCard();
                    BeepFingerNeeded();
                }
                else
                {
                    pkPersonWaitingForFinger = null;
                    if (personAllowed)
                    {
                        cardReader.AllowGateForCard();
                        Thread.Sleep(200);
                        BeepNormal();
                    }
                    else
                    {
                        if (WorkflowRow.tblEventRow.IsfkTwinEventNull() && !Settings.Default.AllowExitToAll)
                        {
                            cardReader.DenyGateForCard();
                            workflowEntrance[WorkflowRow.pkWorkflow] = false;
                            BeepNotAllowed();
                        }
                        else{
                            cardReader.AllowGateForCard();
                            Thread.Sleep(200);
                            BeepNormal();
                        }

                    }

                }

                if (fingerScanner != null && isFingerRequired)
                {
                    fingerScanner.WaitForFingerprint(FingerTimeoutAfterCard);
                }


                //if need to show person in monitoring
                if (pkPerson != null && pkPersonWaitingForFinger == null && !personAllowed)
                {
                    OnEventOccured();
                    eventLogTable.Clear();
                }
            }
            catch (Exception ex)
            {

                cardReader.DenyGateForCard();
                BeepCustom(Beeps.TestLong);
                WriteLog("CardDetected Event, " + ex.Message, eventLevelConstants.Error);
                workflowEntrance[WorkflowRow.pkWorkflow] = false;
            }
        }

        private void FingerDetected(object sender, FingerPrintDetectedEventArgs e)
        {
            try
            {
                if (!WorkflowRow.IsfkTwinWorkflowNull()
                    && workflowEntrance[WorkflowRow.fkTwinWorkflow]){
                        BeepNotAllowed();
                        WriteLog("Duplicate entrance Detected", eventLevelConstants.Info);
                        return;

                }
                workflowEntrance[WorkflowRow.pkWorkflow] = true;

                //var fingerData = e.rawImage;
                bool personAllowed;
                var reason = "";
                string pkPerson;
                var isWaitingForFinger = false;

                //if was waiting for fingerprint
                if (pkPersonWaitingForFinger != null || (WorkflowTypeConstants)WorkflowRow.WorkflowType == WorkflowTypeConstants.CheckCardThenFinger)
                {
                    isWaitingForFinger = true;
                    if (pkPersonWaitingForFinger != null)
                    {
                        var fps = new FingerPrintEngineServerClient("TcpBinding");
                        if (fps.ClientCredentials != null){
                            fps.ClientCredentials.Windows.ClientCredential.Domain = Settings.Default.Domain;
                            fps.ClientCredentials.Windows.ClientCredential.UserName = Settings.Default.Username;
                            fps.ClientCredentials.Windows.ClientCredential.Password = Settings.Default.Password;
                        }

                        try
                        {
                            string error;
                            fps.Open();
                            personAllowed = fps.Verify(out error, pkPersonWaitingForFinger, e.rawImage);
                            //personAllowed = PersonEventBuisness.CheckPersonFinger(pkPersonWaitingForFinger, e.rawImage, (WorkflowLearningModeConstants)WorkflowRow.WorkflowLearningMode, out reason);
                            reason = !personAllowed ? "اثر انگشت و کارت مطابقت ندارد" : "";
                        }
                        catch (Exception ex)
                        {
                            WriteLog(ex.Message, eventLevelConstants.Error);
                            personAllowed = false;
                            //reason = "خطا در نرم افزار";
                        }
                        finally
                        {
                            fps.Close();
                        }





                        pkPerson = pkPersonWaitingForFinger;
                        if (personAllowed)
                        {
                            pkPersonWaitingForFinger = null;
                        }
                    }
                    else //Finger pressed before the card or no card
                    {
                        //update: never happens if AlwaysCheckFinger=false because if finger is required 
                        //only after the card then we do not check finger before the card is detected
                        BeepFingerPressedBeforeCard();
                        fingerScanner.WaitForFingerprint(0);
                        return;
                    }

                }
                else
                {
                    var fps = new FingerPrintEngineServerClient("TcpBinding");
                    if (fps.ClientCredentials != null){
                        fps.ClientCredentials.Windows.ClientCredential.Domain = Settings.Default.Domain;
                        fps.ClientCredentials.Windows.ClientCredential.UserName = Settings.Default.Username;
                        fps.ClientCredentials.Windows.ClientCredential.Password = Settings.Default.Password;
                    }

                    try
                    {
                        string error;
                        fps.Open();
                        pkPerson = fps.IdentifyPerson(out error, e.rawImage);
                        if (pkPerson == string.Empty)
                        {
                            personAllowed = false;
                            reason = "اثر انگشت وجود نداشت";
                        }
                        else
                        {
                            var dummy = false;
                            personAllowed = PersonEventBuisness.CheckPersonByCard(ref pkPerson, ref dummy, ExternalCheckFunc, WorkflowRow.tblEventRow.fkEventType, WorkflowRow.fkEvent, out reason);
                            //personAllowed = PersonEventBuisness.CheckPersonByFinger(fingerData, out pkPerson, WorkflowRow.pkWorkflow, reason);
                        }
                    }
                    catch (Exception ex)
                    {
                        pkPerson = string.Empty;
                        WriteLog(ex.Message, eventLevelConstants.Error);
                        personAllowed = false;
                        reason = "خطا در نرم افزار";
                    }
                    finally
                    {
                        fps.Close();
                    }

                    if (!string.IsNullOrWhiteSpace(pkPerson))
                        EventLogBusiness.ReportPersonAccessResult(pkPerson, ref eventLogTable, WorkflowRow.pkWorkflow, personAllowed ? EventResultConstants.Authorized : EventResultConstants.Denied, reason, WorkflowRow.tblEventRow.EventName);
                }


                if (personAllowed)
                {
                    if (isWaitingForFinger)
                        cardReader.AllowGateForCard();
                    else
                        cardReader.AllowGateCustom();
                }
                else
                {

                    BeepNotAllowed();

                    if (!string.IsNullOrEmpty(pkPerson))
                    {
                        if (WorkflowRow.tblEventRow.IsfkTwinEventNull() && !Settings.Default.AllowExitToAll)
                            cardReader.DenyGateForCard();
                        else
                            cardReader.AllowGateForCard();
                    }
                    workflowEntrance[WorkflowRow.pkWorkflow] = false;

                }

                if (!string.IsNullOrEmpty(pkPerson) && !personAllowed)
                {
                    OnEventOccured();
                    eventLogTable.Clear();
                }
                if (!personAllowed)
                    fingerScanner.WaitForFingerprint(0);
            }
            catch (Exception ex)
            {
                WriteLog("FingerDetected Event, " + ex.Message, eventLevelConstants.Error);
                workflowEntrance[WorkflowRow.pkWorkflow] = false;
                ResumeFingerCheck();
            }

        }

        private void FingerWaitTimeoutExpired(object sender, EventArgs e)
        {
            try
            {

                if (pkPersonWaitingForFinger == null)
                {
                    WriteLog("finger wait timed out but not waiting for person!", eventLevelConstants.Error);
                    ResumeFingerCheck();
                    return;
                }
                EventLogBusiness.ReportPersonAccessResult(pkPersonWaitingForFinger, ref eventLogTable, WorkflowRow.pkWorkflow, EventResultConstants.TimeoutFingerWait, "اتمام زمان انتظار اثر انگشت", WorkflowRow.tblEventRow.EventName);
                BeepTimeoutExpired();
                OnEventOccured();

                pkPersonWaitingForFinger = null;

            }
            catch (Exception ex)
            {
                WriteLog("fingerWaitTimeout event, "+ex.Message, eventLevelConstants.Error);
            }
            finally{
                workflowEntrance[WorkflowRow.pkWorkflow] = false;
                ResumeFingerCheck();
            }
        }

        private void PersonEntered(object sender, string cardID)
        {
            try
            {

                var pkPerson = ((AccessControlDS.tblEventLogRow)eventLogTable.Rows[0]).fkPerson;
                EventLogBusiness.ReportPersonAccessResult(pkPerson, ref eventLogTable, WorkflowRow.pkWorkflow, EventResultConstants.Success, "", WorkflowRow.tblEventRow.EventName);
                OnEventOccured();

                pkPersonWaitingForFinger = null;
                eventLogTable.Clear();


            }
            catch (Exception ex)
            {
                WriteLog("PersonEntered event, " + ex.Message, eventLevelConstants.Error);
            }
            finally{
                workflowEntrance[WorkflowRow.pkWorkflow] = false;
                ResumeFingerCheck();
            }
        }

        private void PersonNotEntered(object sender, string cardID)
        {
            try
            {
                var pkPerson = ((AccessControlDS.tblEventLogRow)eventLogTable.Rows[0]).fkPerson;
                EventLogBusiness.ReportPersonAccessResult(pkPerson, ref eventLogTable, WorkflowRow.pkWorkflow, EventResultConstants.TimeoutEntrance, "اتمام مهلت عبور", WorkflowRow.tblEventRow.EventName);
                BeepTimeoutExpired();
                OnEventOccured();

                pkPersonWaitingForFinger = null;
            }
            catch (Exception ex)
            {
                WriteLog("PersonNotEntered event, " + ex.Message, eventLevelConstants.Error);
            }finally{
                workflowEntrance[WorkflowRow.pkWorkflow] = false;
                ResumeFingerCheck();
            }
        }

        private void FingerScannerConnectionChanged(object sender, bool connected)
        {
            OnDeviceConnectionChanged(fingerScanner.pkDevice, connected);
        }

        private void GateConnectionChanged(object sender, bool connected)
        {
            OnDeviceConnectionChanged(cardReader.pkDevice, connected);
        }

        private void GateDataTransfered(object sender, string data)
        {
            OnDataTransfered(data);
        }

        private void WriteLog(string message, eventLevelConstants level)
        {
            LogFile.WriteLog(LogFile.DefaultLogfileLocation + "WorkflowEngine\\" + DateTime.Now.Date.ToString("yyyyMMdd") + "\\WF" + WorkflowRow.WorkflowName + ".txt", level, message);
        }

        public void End()
        {
            if (cardReader != null) cardReader.Disconnect();
            if (fingerScanner != null) fingerScanner.Disconnect();
        }

        #endregion

        #region Beeps

        public void BeepNormal()
        {
            BeepCustom(Beeps.Normal);
        }

        public void BeepNotAllowed()
        {
            BeepCustom(Beeps.DoubleLong);
        }

        public void BeepFingerNeeded()
        {
            BeepCustom(Beeps.DoubleShort);
        }

        public void BeepTimeoutExpired()
        {
            BeepCustom(Beeps.Triple);
        }

        public void BeepFingerPressedBeforeCard()
        {
            BeepCustom(Beeps.Triple);
        }

        public void BeepCustom(Beeps beepType)
        {
            if (UseFingerScannerBuzzer && fingerScanner != null)
                fingerScanner.Beep(beepType);
            else
                cardReader.Beep(beepType);
        }

        #endregion
    }
}
